using System;
using System.Threading;
using System.Text;
using System.Collections;
using System.Globalization;
using GeoFramework;
using System.ComponentModel;
#if !PocketPC || DesignTime
using System.Drawing.Design;
using System.Windows.Forms.Design;
using System.Runtime.Serialization;
#endif

namespace GeoFramework.Gps
{

	/// <summary>Represents information about all detectable GPS satellites.</summary>
	/// <remarks>
	/// This collection class is used to organize information for up to sixteen GPS
	/// satellites. Statistics about the number of active, tracked and fixed satellites is
	/// available, along with accessors to retrieve one satellite object from the
	/// collection.  This collection is thread-safe; the internal store is locked immediately
	/// before modifications are made.
	/// </remarks>
#if !PocketPC
    [TypeConverter(typeof(ExpandableObjectConverter))]
    [Serializable()]
#endif
	public sealed class SatelliteCollection : IList, ICollection, IEnumerable 
#if !PocketPC || Framework20
		, ICustomTypeDescriptor
#endif
	{
		#region  Private Variables 

		private ArrayList pItems;
		private bool pIsReadOnly;
		//private bool pIsSynchronized;
        // private bool pIsEventsEnabled = false;

		#endregion

		public static readonly SatelliteCollection Empty = new SatelliteCollection(0, true);

		#region Constructors

		internal SatelliteCollection(SatelliteCollection source)
		{
			// And set the inner list
			pItems = ArrayList.Synchronized(new ArrayList(source));			
		}

		/// <summary>Creates a new instance.</summary>
		public SatelliteCollection() 
			: this(8)
		{
		}

		public SatelliteCollection(int capacity)
		{
			pItems = new ArrayList(capacity);
		}

		internal SatelliteCollection(int capacity, bool isReadOnly)
			: this(capacity)
		{
			pIsReadOnly = isReadOnly;
		}

		internal SatelliteCollection(SatelliteCollection satellites, bool isReadOnly) 
			: this(satellites.Count)
		{
			// Add the satellites in
			pItems.AddRange(satellites);
			// And flag readonly or not
			pIsReadOnly = isReadOnly;
		}	

		#endregion

		#region Static Members

		//public static readonly SatelliteCollection Empty = new SatelliteCollection(0);

		public static SatelliteCollection Random()
		{
			return Random(DateTime.Now.Millisecond);
		}

		public static SatelliteCollection Random(int seed)
		{
//			SatelliteCollection NewSatellites = new SatelliteCollection(8);
//			NewSatellites.Add(new Satellite(25, RandomAngle.Add(44).Normalize(), 62, 46, true));
//			NewSatellites.Add(new Satellite(1, RandomAngle.Add(59).Normalize(), 55, 48, true));
//			NewSatellites.Add(new Satellite(14, RandomAngle.Add(86).Normalize(), 19, 41, true));
//			NewSatellites.Add(new Satellite(30, RandomAngle.Add(39).Normalize(), 11, 29, false));
//			NewSatellites.Add(new Satellite(20, RandomAngle.Add(306).Normalize(), 51, 39, true));
//			NewSatellites.Add(new Satellite(16, RandomAngle.Add(153).Normalize(), 32, 10, false));
//			NewSatellites.Add(new Satellite(11, RandomAngle.Add(235).Normalize(), 18, 21, false));
//			NewSatellites.Add(new Satellite(21, RandomAngle.Add(266).Normalize(), 6, 5, false));

			int[] SatelliteCodes = new int[] {25, 1, 14, 30, 20, 16, 11, 21 };
			int[] SatelliteAzimuths = new int[] {44, 59, 86, 39, 306, 153, 235, 266 };
			int[] SatelliteElevations = new int[] {62, 55, 19, 11, 51, 32, 18, 6 };			
			int[] SatelliteSignals = new int[] {46, 48, 41, 29, 39, 10, 21, 5 };			

			SatelliteCollection NewSatellites = new SatelliteCollection();
#if PocketPC
			Azimuth RandomAzimuth = Azimuth.Random();
#else
			Azimuth RandomAzimuth = Azimuth.Random(seed);
#endif
            for (int index = 0; index < 8; index++)
			{
				NewSatellites.Add(new Satellite(SatelliteCodes[index], 
					RandomAzimuth.Add(SatelliteAzimuths[index]).Normalize(),
					new Elevation(SatelliteElevations[index]),
					SatelliteSignals[index],
					SatelliteSignals[index] > 35));

			}
//
//			// Get an index to the satellite PRN array
//			int SatelliteIndex = -1;
//			// Add all fixed satellites
//			int FixedCount = Generator.Next(0, 9);
//			for(int count = 0; count < FixedCount; count++)
//			{
//				// Add a new satellite
//				SatelliteIndex++;
//				NewSatellites.Add(new Satellite(SatelliteCodes[SatelliteIndex],
//					new Azimuth(Generator.NextDouble() * 360.0), new Elevation(Generator.NextDouble() * 90.0),
//					new SignalToNoiseRatio((int)Generator.Next(20, 40)), true));				
//			}
//			// Add all tracked satellites until we're at 12
//			int TrackedCount = Math.Max(Generator.Next(0, 9), 12 - FixedCount);
//			for(int count = 0; count < TrackedCount; count++)
//			{
//				// Add a new satellite
//				SatelliteIndex++;
//				NewSatellites.Add(new Satellite(SatelliteCodes[SatelliteIndex],
//					new Azimuth(Generator.NextDouble() * 360.0), new Elevation(Generator.NextDouble() * 90.0),
//					new SignalToNoiseRatio((int)Generator.Next(5, 30)), false));				
//			}
			return NewSatellites;
		}

		#endregion

		public static SatelliteCollection Synchronized(SatelliteCollection c)
		{
			return new SatelliteCollection(c);
		}

		public void Sort()
		{
			Sort(SatellitePseudorandomNumberComparer.Default);
		}

		public void Sort(IComparer comparer)
		{
			pItems.Sort(comparer);
		}

		/// <summary>Returns a shallow copy of the current collection.</summary>
		public SatelliteCollection Clone()
		{
			return (SatelliteCollection)base.MemberwiseClone();
		}

		/// <summary>Adds a new satellite to the collection.</summary>
		public int Add(Satellite value)
		{
			if(pIsReadOnly)
				throw new InvalidOperationException("The collection is read-only and cannot be modified.");

			// And add to the collection
			return pItems.Add(value);
		}

		/// <summary>Removes a satellite from the collection.</summary>
		public void Remove(Satellite value)
		{
			if(pIsReadOnly)
				throw new InvalidOperationException("The collection is read-only and cannot be modified.");

			// And remove it
			pItems.Remove(value);
		}

		/// <summary>Removes a satellite at the specified index from the collection.</summary>
		public void RemoveAt(int index)
		{
			// Remove the item at the index
			Remove(this[index]);
		}

		/// <summary>Adds a satellite to the collection at the specified index.</summary>
		public void Insert(int index, Satellite value)
		{
			if(pIsReadOnly)
				throw new InvalidOperationException("The collection is read-only and cannot be modified.");

			pItems.Insert(index, value);
		}

		/// <summary>Removes all satellites from the collection.</summary>
		public void Clear()
		{
			if(pIsReadOnly)
				throw new InvalidOperationException("The collection is read-only and cannot be modified.");
		}

		/// <summary>Returns the likelihood that a fix will be obtained or sustained.</summary>
		public FixLikelihood FixLikelihood
		{
			get
			{
				SignalToNoiseRatio TotalStrength = TotalSignalStrength;
				// Calculate the fix likelihood
				if (TotalStrength <= 80)
					return FixLikelihood.Unlikely;
				else if (TotalStrength <= 100)
					return FixLikelihood.Possible;
				else if (TotalStrength <= 135)
					return FixLikelihood.Likely;
				else
					return FixLikelihood.Certain;
			}
		}

		/// <summary>
		/// Returns a sub-collection of satellites currently involved in calculating the
		/// current location.
		/// </summary>
		public SatelliteCollection FixedSatellites
		{
			get
			{
                SatelliteCollection pFixedSatellites = new SatelliteCollection();
                lock (pItems.SyncRoot)
                {
                    foreach (Satellite satellite in pItems)
                        if (satellite.IsFixed)
                            pFixedSatellites.Add(satellite);
                }
				return pFixedSatellites;
			}
		}


        /// <summary>
        /// Returns a sub-collection of satellites currently providing additional
        /// precision information to the GPS receiver.
        /// </summary>
        public SatelliteCollection DifferentialGpsSatellites
        {
            get
            {
                SatelliteCollection pDgpsSatellites = new SatelliteCollection();
                lock (pItems.SyncRoot)
                {
                    foreach (Satellite satellite in pItems)
                        if (satellite.IsDifferentialGpsSatellite)
                            pDgpsSatellites.Add(satellite);
                }
                return pDgpsSatellites;
            }
        }

		/// <summary>Returns a sub-collection of satellites whose signals are being detected.</summary>
		public SatelliteCollection TrackedSatellites
		{
			get
			{
                SatelliteCollection pTrackedSatellites = new SatelliteCollection();
                lock (pItems.SyncRoot)
                {
                    foreach (Satellite satellite in pItems)
                        if (satellite.SignalToNoiseRatio.Value > 0)
                            pTrackedSatellites.Add(satellite);
                }
				return pTrackedSatellites;
			}
		}

		/// <summary>Returns the current number of known GPS satellites.</summary>
		/// <value>An <strong>Integer</strong>.</value>
		/// <remarks>
		/// GPS devices report the location and unique identifier for each satellite, whether
		/// it is currently or previously detected. This property is typically used to indicate the
		/// maximum number of satellites which <em>could possibly</em> be involved in a fix if the
		/// device were in wide open sky. For the number of satellites whose satellite signals have
		/// been detected, see the TrackedCount property.
		/// </remarks>
		public int Count
		{
			get
			{
				return pItems.Count;
			}
		}

		/// <summary>Returns a <strong>Satellite</strong> object at the specified index.</summary>
		public Satellite this[int index]
		{
			get
			{
				return (Satellite)pItems[index];
			}
			set
			{
				if(pIsReadOnly)
					throw new InvalidOperationException("The collection is read-only and cannot be modified.");

				pItems[index] = value;
			}
		}

		/// <summary>Returns a satellite by its pseudo-random number.</summary>
		public Satellite GetSatellite(int pseudorandomNumber) 
		{
				foreach (Satellite satellite in pItems)
				{
					if (satellite.PseudorandomNumber == pseudorandomNumber)
						return satellite;
				}
			return null;
		}

		/// <summary>Returns the friendly name of the satellite.</summary>
		public override string ToString()
		{
			StringBuilder Result = new StringBuilder();
			// Dumps out all satellite information

				foreach (Satellite Satellite in pItems)
					if (Satellite.PseudorandomNumber != 0)
						Result.Append(Satellite.ToString() + "\r\n");
			return Result.ToString();
		}

		/// <summary>Returns a new, read-only collection containing the specified satellites.</summary>
		public static SatelliteCollection ReadOnly(SatelliteCollection satellites)
		{
			return new SatelliteCollection(satellites, true);
		}

		/// <summary>Returns the combined signal strength of all satellites.</summary>
		public SignalToNoiseRatio TotalSignalStrength
		{
			get
			{
				int SignalStrength = 0;
					foreach (Satellite Satellite in pItems)
						SignalStrength += Satellite.SignalToNoiseRatio.Value;
				return new SignalToNoiseRatio(SignalStrength);
			}
		}

		/// <summary>Copies the current collection to the specified array.</summary>
		public void CopyTo(Array array, int index)
		{
			pItems.CopyTo(array, index);
		}

		/// <summary>Returns whether the specified satellite exists in the collection.</summary>
		public bool Contains(Satellite item)
		{
			return pItems.Contains(item);
		}

		/// <summary>
		/// Returns a satellite from the collection based on the satellite's unique code.
		/// </summary>
		/// <param name="pseudorandomNumber">The ID of the satellite to locate.</param>
		/// <returns>A <strong>Satellite</strong> matching the specified pseudo-random number, or null if no match was found.</returns>
		public bool Contains(int pseudorandomNumber)
		{
			return IndexOf(pseudorandomNumber) != -1;
		}

		/// <summary>Returns the position of the specified satellite in the collection.</summary>
		public int IndexOf(Satellite value)
		{
			return pItems.IndexOf(value);
		}

		public int IndexOf(int pseudorandomNumber)
		{
				for(int index = 0; index < Count; index++)
				{
					if(((Satellite)pItems[index]).PseudorandomNumber == pseudorandomNumber)
						return index;
				}
			return -1;
		}

		/// <summary>
		/// Updates satellites in the current collection to match the satellites in the specified collection.
		/// </summary>
		/// <param name="values">A <strong>SatelliteCollection</strong> containing satellite objects to merge.</param>
		/// <remarks>This method is typically used to update a collection when newer satellite information
		/// has become available.  Satellite objects are matched up by their pseudo-random code, then replaced
		/// in the current instance.  Existing satellite objects, which did not already exist in the specified
		/// collection, are left untouched.</remarks>
		/// <returns>A <strong>Boolean</strong>, <strong>True</strong> if the current collection was modified in any way,
		/// or <strong>False</strong> if no changes were detected.</returns>
		public bool MergeWith(SatelliteCollection values)
		{
			bool ChangesDetected = false;

			//lock(values.SyncRoot)
			{
				foreach(Satellite newSatellite in values)
				{
					Satellite ExistingSatellite = null;

						// Try to find an existing item
						foreach(Satellite current in pItems)
						{
							if(current.PseudorandomNumber == newSatellite.PseudorandomNumber)
							{
								ExistingSatellite = current;
								break;
							}
						}
						// Does the collection contain this satellite?
						if(ExistingSatellite == null)
						{
							// No.  Add it in
							ChangesDetected = true;
							pItems.Add(newSatellite);
						}
						// Yes.  Do the objects hold the same value?
						else if(!ExistingSatellite.Equals(newSatellite))
						{
							// No.  Replace the item in the collection
							ChangesDetected = true;
							int Index = pItems.IndexOf(ExistingSatellite);
							pItems[Index] = newSatellite;
						}
						else if(!newSatellite.SignalToNoiseRatio.IsEmpty)
						{
							// No.  Replace the item in the collection
							ChangesDetected = true;
							int Index = pItems.IndexOf(ExistingSatellite);
							pItems[Index] = newSatellite;
						}
					
				}
			}

			// Return whether anything was changed
			return ChangesDetected;
		}

		#region IList Members

		bool IList.IsReadOnly
		{
			get
			{
				return false;
			}
		}

		object IList.this[int index]
		{
			get
			{
				return pItems[index];
			}
			set
			{
				pItems[index] = value;
			}
		}

		void IList.RemoveAt(int index)
		{
			this.RemoveAt(index);
		}

		void IList.Insert(int index, object value)
		{
			this.Insert(index, (Satellite)value);
		}

		void IList.Remove(object value)
		{
			this.Remove((Satellite)value);
		}

		bool IList.Contains(object value)
		{
			return this.Contains((Satellite)value);
		}

		int IList.IndexOf(object value)
		{
			return this.IndexOf((Satellite)value);
		}

		int IList.Add(object value)
		{
			return this.Add((Satellite)value);
		}

		bool IList.IsFixedSize
		{
			get
			{
				return false;
			}
		}

		#endregion

		#region ICollection Members

		bool ICollection.IsSynchronized
		{
			get
			{
				return false;
			}
		}

		int ICollection.Count
		{
			get
			{
				return this.Count;
			}
		}

		void ICollection.CopyTo(Array array, int index)
		{
			this.CopyTo(array, index);
		}

		/// <summary>
		/// Returns an object used to lock the collection during enumeration or modification
		/// operations.
		/// </summary>
		public object SyncRoot
		{
			get
			{
				return pItems.SyncRoot;
			}
		}

		#endregion

		#region IEnumerable Members

		IEnumerator IEnumerable.GetEnumerator()
		{
			return pItems.GetEnumerator();
		}

		#endregion

        #region ICustomTypeDescriptor Members

#if !PocketPC || Framework20

        String ICustomTypeDescriptor.GetClassName()
        {
#if !PocketPC 
			return TypeDescriptor.GetClassName(this, true);
#else
            return null;
#endif
        }

        AttributeCollection ICustomTypeDescriptor.GetAttributes()
        {
#if !PocketPC 
			return TypeDescriptor.GetAttributes(this, true);
#else
            return null;
#endif
        }

        String ICustomTypeDescriptor.GetComponentName()
        {
#if !PocketPC 
			return TypeDescriptor.GetComponentName(this, true);
#else
            return null;
#endif
        }

        TypeConverter ICustomTypeDescriptor.GetConverter()
        {
#if !PocketPC
			return TypeDescriptor.GetConverter(this, true);
#else
            return null;
#endif
        }

        EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
        {
#if !PocketPC
			return TypeDescriptor.GetDefaultEvent(this, true);
#else
            return null;
#endif
        }

        PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
        {
#if !PocketPC
			return TypeDescriptor.GetDefaultProperty(this, true);
#else
            return null;
#endif
        }

        object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
        {
#if !PocketPC
			return TypeDescriptor.GetEditor(this, editorBaseType, true);
#else
            return null;
#endif
        }
#endif

#if !PocketPC || Framework20

        EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
        {
            return TypeDescriptor.GetEvents(this, attributes, true);
        }

        EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
        {
            return TypeDescriptor.GetEvents(this, true);
        }

        object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
        {
            return this;
        }

		PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
		{
			// Create a new collection object PropertyDescriptorCollection
			PropertyDescriptorCollection pds = new PropertyDescriptorCollection(null);

			// Iterate the list of employees
			for( int i=0; i < pItems.Count; i++ )
			{
				// For each employee create a property descriptor 
				// and add it to the 
				// PropertyDescriptorCollection instance
				SatellitePropertyDescriptor pd = new 
					SatellitePropertyDescriptor(this, i);
				pds.Add(pd);
			}
			return pds;		
        }

		PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties() 
		{
			// Create a new collection object PropertyDescriptorCollection
			PropertyDescriptorCollection pds = new PropertyDescriptorCollection(null);

			// Iterate the list of employees
			for( int i=0; i < pItems.Count; i++ )
			{
				// For each employee create a property descriptor 
				// and add it to the 
				// PropertyDescriptorCollection instance
				SatellitePropertyDescriptor pd = new 
					SatellitePropertyDescriptor(this, i);
				pds.Add(pd);
			}
			return pds;
		}
#endif

		#endregion

	}

	internal sealed class SatellitePropertyDescriptor : System.ComponentModel.PropertyDescriptor
	{
		private SatelliteCollection pSatelliteCollection;
		private int pIndex = -1;

		public SatellitePropertyDescriptor(SatelliteCollection collection, int index) : base("#" + index.ToString(), null)
		{
			pSatelliteCollection = collection;
			pIndex = index;
		} 

#if !PocketPC
		public override AttributeCollection Attributes
		{
			get 
			{ 
				return new AttributeCollection(null);
			}
		}
#endif

		public override bool CanResetValue(object component)
		{
			return true;
		}

		public override Type ComponentType
		{
			get 
			{ 
				return pSatelliteCollection.GetType();
			}
		}

		public override string DisplayName
		{
			get 
			{
				Satellite Satellite = pSatelliteCollection[pIndex];
				return Satellite.Name;
			}
		}

#if !PocketPC
		public override string Description
		{
			get
			{
				Satellite Satellite = pSatelliteCollection[pIndex];
				return Satellite.Azimuth.ToString() + CultureInfo.CurrentCulture.TextInfo.ListSeparator + " "
					+ Satellite.Elevation.ToString() + CultureInfo.CurrentCulture.TextInfo.ListSeparator + " "
					+ Satellite.SignalToNoiseRatio.ToString();
			}
		}
#endif

		public override object GetValue(object component)
		{
			return pSatelliteCollection[pIndex];
		}

		public override bool IsReadOnly
		{
			get 
			{ 
				return true;  
			}
		}

		public override string Name
		{
			get 
			{ 
				return "#" + pIndex.ToString(); 
			}
		}

		public override Type PropertyType
		{
			get 
			{ 
				return pSatelliteCollection[pIndex].GetType(); 
			}
		}

		public override void ResetValue(object component) {}

		public override bool ShouldSerializeValue(object component)
		{
			return true;
		}

		public override void SetValue(object component, object value)
		{
			pSatelliteCollection[pIndex] = (Satellite)value;
		}
	
	}

	public sealed class SatelliteCollectionEventArgs : EventArgs
	{
		private SatelliteCollection oSatelliteCollection;

		/// <summary>
		/// Creates a new instance.
		/// </summary>
		/// <param name="SatelliteCollection"></param>
		public SatelliteCollectionEventArgs(SatelliteCollection satelliteCollection)
		{
			if(satelliteCollection != null)
				oSatelliteCollection = SatelliteCollection.ReadOnly(satelliteCollection);
		}

		public new static readonly SatelliteCollectionEventArgs Empty = new SatelliteCollectionEventArgs(null);

		/// <summary>
		/// Indicates which SatelliteCollection is the target of the event.
		/// </summary>
		/// <value>A read-only <strong>SatelliteCollection</strong> object containing modified information.</value>
		public SatelliteCollection SatelliteCollection 
		{
			get
			{
				return oSatelliteCollection;
			}
		}

	}

	public delegate void SatelliteCollectionEventHandler(object sender, SatelliteCollectionEventArgs e);
}